#include <stdio.h>
#include <signal.h>
#include <error.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

//简单TCP网络程序，可以实现简单聊天室

//******************服务器端********************
//TCP协议是建立连接的可靠传输协议
//1.绑定Ip 和 端口号
//2.被动打开，使可以被建立连接
//3.接受客户端的连接请求
//开始进行事件循环
//  4.接收来自客户端的请求
//  5.开始计算处理客户端发来的请求
//   a)因为这里是回显服务，没有过多的计算处理,只做以下处理
//   b)将客户端的请求显示在标准输出上
//   c)再将请求发回给客户端
//  6.对客户端发来的请求进行响应

//采多进程的方法完成一个服务器可以同时接受多个用户的请求与响应
//注意三个小点
//1.子进程的资源回收的问题
//2.子进程和父进程有各自的执行逻辑，当子进程结束后，不应该和父进程执行相同的逻辑
//3.socket文件的关闭，子进程退出后，自动关闭文件描述符，父进程应该在创建出子进程后就应该关闭文件描述符


//处理一次连接
void ProcessConnect(int listen_socket)
{
  pid_t pid=fork();
  if(pid<0)
  {//创建子进程失败
    return;
  }
  if(pid==0)
  {//**************子进程***************************
    //处理客户端发来的请求

    char buf[1024]={0};
    sockaddr_in  peer;
    //用来处理一次连接。需要循环的处理
    while(1)
    {
      //7.接收来自客户端的请求
      ssize_t read_size=read(listen_socket,buf,sizeof(buf)-1);

      if(read_size<0)
      {
        continue;
      }
      if(read_size==0)
      {
        printf("client discount!\n");
        //用来标识用户已经关闭连接
        close(listen_socket);
        //子进程处理完该用户的所有请求后，不应该直接return
        //这样的话，就会回到main()函数中继续循环，而开始接受其他用户的连接请求
        //这样就会和父进程执行相同的逻辑
        
        close(listen_socket);//不用手动关闭也可以，因为子进程退出后，文件描述符会自动关闭
        exit(0);
      }

      buf[read_size]='\0';

      //8.将客户端的请求显示再标准输出上 
      printf("peer %s:%d  say %s",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);

      //9.将请求发送给客户端

      write(listen_socket,buf,read_size);
    }
  }
  else
  {//*********************父进程****************************

    close(listen_socket);
    //直接退出，回到main()函数中继续循环，接受其他用户的请求

    //因为父进程需要回收子进程的资源
    //但是无论是wait()还是waitpid(),都会进行阻塞式等待
    //并不能达到一个服务器可以同时执行多个用户的请求

    //这里比较优雅的方式为，父进程直接忽略掉子进程的退出信号SIGCHILD

    //则父进程可以先回到main()函数执行自己的逻辑
    //在main()函数中进行信号忽略处理

    return;
  }
}


// ./service [IP] [Port]
int main(int argc,char * argv[])
{
  //1.检查命令行参数
  if(argc!=3)
  {
    printf("Usage: ./service [IP] [Port]\n");
    return 1;
  }
  
  //2.创建socket 
  int fd=socket(AF_INET,SOCK_STREAM,0);
  if(fd<0)
  {
    perror("socket");
    return 1;
  }
  
  //3.将要绑定的IP 和 Prot 存储在 sockaddr_in 结构体中
  
  sockaddr_in addr;
  addr.sin_family=AF_INET;
  socklen_t addr_len=sizeof(addr);
  //将点分十进制的IP地址 转化 为32位数字
  addr.sin_addr.s_addr=inet_addr(argv[1]);
  //将端口号转位网络字节序
  addr.sin_port=htons(atoi(argv[2]));


  //4.服务器建立连接
  int bind_ret=bind(fd,(sockaddr *)&addr,addr_len);
  if(bind_ret<0)
  {
    perror("bind");
    return 1;
  }

  //5.服务器开始被动打开，处于可以被建立连接状态
  int listen_ret=listen(fd,5);
  if(listen_ret<0)
  {
    perror("listen");
    return 1;
  }

  //开始事件循环
  
  //父进程忽略掉子进程退出的信号
  //内核自动回收子进程的资源
  signal(SIGCHLD,SIG_IGN);

  while(1)
  {
    //6.接受客户端的连接请求
    
    //fd 这个socket用来监听
    int listen_socket=accept(fd,(sockaddr *)&addr,&addr_len);
    //这里的返回值是一个新的 socket 将内核态建立的连接放到用户态执行
    //后续信息传输建立在这个socket
    if(listen_socket<0)
    {
      perror("accept");
      continue;
    }
    //7.处理一次连接
    ProcessConnect(listen_socket);
  }
  close(fd);
  return 0;
}
